/*
 * This file is part of JFlowMap.
 *
 * Copyright 2009 Ilya Boyandin
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package fons.navigator.util.map;

import java.awt.geom.Point2D;


import com.google.common.base.Function;
import com.google.common.collect.Iterables;

import fons.navigator.util.Point;

/**
 * @author Ilya Boyandin
 */
public enum MapProjections implements MapProjection {

	NONE {
		@Override
		public Point2D project(double lon, double lat) {
			return new Point2D.Double(lon, lat);
		}
	},

	FLIPY {
		@Override
		public Point2D project(double lon, double lat) {
			return new Point2D.Double(lon, -lat);
		}
	},

	MERCATOR {
		// if the scale is smaller than 100 short edges will not be visible
		private final static double SCALE = 100;
		private final static boolean INVERT_Y = true;

		@Override
		public Point2D project(double lon, double lat) {
			return new Point2D.Double(
					SCALE * lon / 180,
					SCALE * (INVERT_Y ? -1 : 1) * (lat > 85 ?
							1 : (lat < -85 ?
									-1 : Math.log(Math.tan(Math.PI / 4 + radians(lat) / 2)) / Math.PI)
							));
		}

	},

	WINKELTRIPEL {
		private final double phi1 = Math.acos(2/Math.PI);
		private final double cos_phi1 = Math.cos(phi1);

		public Point2D project(double lat, double lon) {
			double lplam = radians(lat);
			double lpphi = radians(lon);

			double c = 0.5 * lplam;
			double cos_lpphi = Math.cos(lpphi);
			double alpha = Math.acos(cos_lpphi * Math.cos(c));

			double x, y;

			if (alpha != 0) {
				double sinc_alpha = sinc(alpha);
				x = 2.0 * cos_lpphi * Math.sin(c) / sinc_alpha;
				y = Math.sin(lpphi) / sinc_alpha;
			} else {
				x = y = 0.0;
			}

			x = (x + lplam * cos_phi1) * 0.5;
			y = (y + lpphi) * 0.5;

			y = -y;

			return new Point2D.Double(x, y);
		}

		private double sinc(double x) { return Math.sin(x)/x; }

	}

	;


	private static double radians(double degrees) {
		return degrees * Math.PI / 180;
	}


	public static Iterable<Point> projectAll(Iterable<Point> points, final MapProjection mapProjection) {
		return Iterables.transform(points, new Function<Point, Point>() {
			@Override
			public Point apply(Point from) {
				return Point.valueOf(mapProjection.project(from.x(), from.y()));
			}
		});
	}

}
